#  Copyright (c) 2007 Microsoft Corporation. All rights reserved.
$:.unshift File.join(File.dirname(__FILE__),"..")

require 'rexml/document'
require 'time'
# require 'rexml/encodings/ISO-8859-1.rb'
# require 'rexml/encodings/UTF-8.rb'
require 'rubygems'
require 'vmxt2tfs/elements'

module VMXT2TFS
  #This class parses the VMP and VM-XT xml for the data needed to generate the TFS template
  class CollabXTParser
    attr_reader :milestones,:project_name
    attr_reader :activities,:products,:areas,:initial_products,:iterations,:roles
    include REXML
    #XPath to get all the Meilenstein elements from the vmp
    XPATH_VMP_MILESTONE= "/VMProjekt/PlanungsInformation/Meilenstein"
    #XPath to get the project name
    XPATH_VMP_PROJECTNAME="/VMProjekt/ProjektInformation/Bezeichnung"
    #XPath to get all the AEP elements from the VM-XT xml
    XPATH_VMXT_AEP= "/V-Modell/Ablaufbausteine/Ablaufbaustein/Ablaufentscheidungspunkt"
    #XPath to get all the Entscheidungspunkt elements from the VM-XT xml
    XPATH_VMXT_EPUNKT= "/V-Modell/Entscheidungspunkte/Entscheidungspunkt"
    #XPath to get all the Produkt elements from the VM-XT xml
    XPATH_VMXT_PRODUKT= "/V-Modell/Vorgehensbausteine/Vorgehensbaustein/Produkte/Produkt"
    #XPath to get all the Aktivität elements from the VM-XT xml
    XPATH_VMXT_AKTIVITAET= "/V-Modell/Vorgehensbausteine/Vorgehensbaustein/Aktivitäten/Aktivität"
    #Path to get the roles
    XPATH_VMXT_ROLES="/V-Modell/Vorgehensbausteine/Vorgehensbaustein/Rollen/Rolle"
    #XPath to get all product groups from the VM-XT xml
    XPATH_VMXT_PRODUCT_GROUP= "//Produktgruppe" 
    #XPath to get all actvity groups from the VM-XT xml
    XPATH_VMXT_ACTIVITY_GROUP="//Aktivitätsgruppe"
    #XPath to get the product type
    XPATH_VMXT_PRODUCT_TYPE="/VMProjekt/TailoringInformation/PTRef[@link]"

    #__vmp__ is the XML of the VMP project file. 
    #__vmxt__ is the XML of the tailored VM-XT meta model. 
    #The tailoring information is contained in the VMP data
    def initialize vmp,vmxt,logger=nil
        @logger=logger
        @logger||=VMXT2TFS.setup_logger
        @logger.info("Loading plan data")
        @vmp=Document.new(vmp)
        @logger.info("Loading process metamodel")
        @vmxt=Document.new(vmxt)
        #these come after parsing the vmp
        @milestones=Array.new
        @milestone_index=Hash.new
        #these come from the VM-XT xml
        @activities=Array.new
        @products=Array.new
        @initial_products=Array.new
        @activity_groups=Hash.new
        @product_groups=Hash.new
        @areas=Array.new
        #these variables are used in determining the iteration structure
        @counter_ea=0
        @counter_se=0
        @iterations=Hash.new
        #these iterations will always be there
        VMXT2TFS::Templates::EP_TO_ITERATION.each{|k,v| @iterations[v]=Array.new}
        #what we use for Classification XML
        @current_iteration=""
        #what we're going to use in the default WIs
        @wi_iteration=""
        @roles=Array.new
        @logger.debug("CollabXT initialized")
    end
    #After running this method a CollabXTParser instance will provide the activities, milestones and initial products
    #for the tailoring described in the VMP file using the data in tne VM-XT xml file
    def parse
      #first we parse the VM-XT XML
      #get the product type
      product_type = XPath.match( @vmp,XPATH_VMXT_PRODUCT_TYPE)[0].attributes["link"]
      raise "Unsupported project type" unless Templates::PROJECT_TYPES.include?(product_type)
      parse_product_groups#product groups
      parse_activity_groups#activity groups
      @eps=parse_eps#all the EPs
      aeps_to_eps=parse_aeps #index of AEP to EP
      product_to_activity=parse_activities #index of Produkt=>Aktivitaet
      ep_to_products=parse_products #index of EP=>[Produkt]
      parse_roles
      #then we parse the tailoring information
      #the project name
      @project_name=XPath.match(@vmp,XPATH_VMP_PROJECTNAME)[0].text
      #the milestones. 
      #with the tailoring information we get the EPs that are relevant for this plan
      tailored_eps=parse_milestones(aeps_to_eps)
      #for each milestone, create the activity instances
      @milestone_index.each do |ep_ref,mstones|
       mstones.each do |mstone|
        #get the products associated with the EP
         ep_to_products[ep_ref].each do |product|
           if product_to_activity[product.vmxt_ref]
             @logger.debug("Activity instance from #{ep_ref} for #{mstone.vmxt_ref}")
             #create the activity instance
             @activities<<AktivitaetInstanz.new(:activity=>product_to_activity[product.vmxt_ref],
               :milestone=>mstone)
             @products+=ep_to_products[ep_ref]
           else
             @logger.warn("No activity mapped to product #{product.vmxt_ref}")
           end
         end
       end
      end
      @initial_products=@products.select{ |prod| prod.initial? }
      @initial_products.uniq!
      #and clear up the areas
      @areas=@products.collect { |p| p.group }
      @areas+=@activities.collect { |a| a.group }
      @areas.uniq!
    end
    private
    
    #extracts the information on Entscheidungspunkt elements from the XML
    def parse_eps
      @logger.debug("Parsing EPs")
      t1=Time.now
      eps=Hash.new
      #get all the activities
      XPath.each(@vmxt,XPATH_VMXT_EPUNKT) do |ac|
        params={:vmxt_ref=>ac.attributes["id"],
          :title=>ac.elements["Name"].text,
          :description=>strip_html(ac.elements["Sinn_und_Zweck"].text),
          :number=>ac.elements["Nummer"].text,
        }
        eps[ac.attributes["id"]]=Entscheidungspunkt.new(params)
      end
      @logger.info("EPs parsed in #{Time.now-t1} seconds")
      return eps
    end
    #extracts the information on Ablaufentscheidungspunkt elements from the XML
    #
    #Returns a Hash that maps the AEPs to EPs. 
    def parse_aeps
      @logger.debug("Parsing AEPs")
      t1=Time.now
      aeps_to_eps=Hash.new
      #get all the AEP elements
      XPath.each(@vmxt,XPATH_VMXT_AEP) do |ac|
        aeps_to_eps[ac.attributes["id"]]=ac.elements["EntscheidungspunktRef"].attributes["link"].to_s
      end
      @logger.info("AEPs parsed in #{Time.now-t1} seconds")
      return aeps_to_eps
    end
    #extracts the information on Aktivitaet elements from the XML
    def parse_activities
      @logger.debug("Parsing activities")
      t1=Time.now
      product_to_activities=Hash.new
      #get all the activities
      XPath.each(@vmxt,XPATH_VMXT_AKTIVITAET) do |ac|
        product_ref=ac.elements["ProduktRef"].attributes["link"].to_s
        #get the activity group for this product
        group=@activity_groups[ac.elements["AktivitätsgruppeRef"].attributes["link"].to_s]
        @logger.error("No activity group found for activity #{ac.attributes["id"]}") unless group
        params={:vmxt_ref=>ac.attributes["id"],
          :title=>ac.elements["Name"].text,
          :description=>strip_html(ac.elements["Sinn_und_Zweck"].text),
          :number=>ac.elements["Nummer"].text,
          :product_ref=>product_ref,
          :group=>group
        }
        product_to_activities[product_ref]=Aktivitaet.new(params)
      end
      @logger.info("Activities parsed in #{Time.now-t1} seconds")
      return product_to_activities
    end
    #extracts information on product groups and caches them in a id=>name hash
    def parse_product_groups
      @logger.debug("Parsing product groups")
      t1=Time.now
      product_groups = XPath.match( @vmxt,XPATH_VMXT_PRODUCT_GROUP)
      product_groups.each { |pg| @product_groups[pg.attributes["id"].to_s ]=pg.elements["Name"].text}
      @logger.info("Product groups parsed in #{Time.now-t1} seconds")
    end
    #extracts information on product groups and caches them in a id=>name hash
    def parse_activity_groups
      @logger.debug("Parsing activity groups")
      t1=Time.now
      groups = XPath.match( @vmxt,XPATH_VMXT_ACTIVITY_GROUP)
      groups.each { |pg| @activity_groups[pg.attributes["id"].to_s ]=pg.elements["Name"].text}
      @logger.info("Activity groups parsed in #{Time.now-t1} seconds")
    end
    #extracts the information on Produkt elements from the XML
    def parse_products
      @logger.debug("Parsing products")
      t1=Time.now
      ep_to_products=Hash.new
      #get all the products
      XPath.each(@vmxt,XPATH_VMXT_PRODUKT) do |ac|
       initial=(ac.attributes["Initial"]=="Ja")
        #for the time being we don't do anything with products that have no EP
        if ac.elements["EntscheidungspunktRef"]
          #get all the EP ids
          eps=ac.elements.collect do |elem| 
            if elem.name=="EntscheidungspunktRef" 
              elem.attributes["link"].to_s
            end
          end.compact.uniq
          @logger.debug("#{eps.size} EP ids for #{ac.attributes["id"]}")
          #get the product group for this product
          group=@product_groups[ac.elements["ProduktgruppeRef"].attributes["link"].to_s]
          @logger.error("No product group found for product #{ac.attributes["id"]}") unless group
          #put the constructor parameters together
          params={:vmxt_ref=>ac.attributes["id"],
            :title=>ac.elements["Name"].text,
            :description=>strip_html(ac.elements["Sinn_und_Zweck"].text),
            :number=>ac.elements["Nummer"].text,
            :ep_refs=>eps,
            :initial=>initial,
            :group=>group,
            :vmxt_type=>ac.elements["Name"].text
          }
          #create the product instance
          product=Produkt.new(params)
          #add it to the index for all the EPs it references
          product.ep_refs.each do |ref|
            ep_to_products[ref]||=Array.new
            ep_to_products[ref]<<product
          end
        else
          @logger.debug("Product with no Milestone reference: #{ac.attributes['id']}")
        end
      end
      @logger.info("Products parsed in #{Time.now-t1} seconds")
      return ep_to_products
    end
    
    #extracts the role information
    def parse_roles
      @logger.debug("Parsing roles")
      t1=Time.now
      ep_to_products=Hash.new
      #get all the products   
      XPath.each(@vmxt,XPATH_VMXT_ROLES) do |ac|
        description=strip_html(ac.elements["Beschreibung"].text)
        #split at the soace closest to our limit
        if description.length > 254
          pieces=description.split(' ')
          desc=""
          pieces.each do |piece|
            if desc.length+piece.length<251
              desc<<"#{piece} "
            else
              break
            end
          end
          description="#{desc.chomp(" ")}..."
        end
        @roles<<{:name=>ac.elements["Name"].text,
            :vmxt_ref=>ac.attributes["id"].to_s,
            :description=>description}
      end
      @logger.info("Roles parsed in #{Time.now-t1} seconds")
    end
    #extracts the information on Meilenstein elements from the VMP.
    #
    #Also returns the EP array containing the EPs used in the tailoring and creates an index of EP=>[Meilenstein].
    #
    #It uses the provided AEP=>EP index
    def parse_milestones aeps_to_eps
      @logger.debug("Parsing milestones")
      t1=Time.now
      milestones=XPath.match(@vmp,XPATH_VMP_MILESTONE)
      milestones.each do |el| 
        aep_ref=el.elements["AEPRef"].attributes["link"].to_s
        date_txt=el.elements["Datum"].text
        end_date=Time.parse(date_txt)
        @logger.debug("Parsed '#{date_txt}' into #{end_date}")
        ep_ref=aeps_to_eps[aep_ref]
        iter=determine_iteration(ep_ref)
        type=@eps[ep_ref].title
        description=@eps[ep_ref].description
        mstone=Meilenstein.new(:title=>el.elements["Name"].text,
          :number=>el.elements["Nummer"].text,
          :ep_ref=>ep_ref,
          :vmxt_ref=>el.attributes["id"].to_s,
          :iteration=>iter,
          :vmxt_type=>type,
          :end_date=>end_date,
          :description=>description
        )
        @milestones<<mstone
        @milestone_index[ep_ref]||=Array.new
        @milestone_index[ep_ref]<<mstone
      end
      @logger.info("Milestones parsed in #{Time.now-t1} seconds")
    end
    #strips HTML tags from a given string
  	def strip_html str 
  		str = str.strip || ''
  		str.gsub(/<(\/|\s)*[^>]*>/,'')
  	end
  	#entwicklungsabschnitt name
    def current_ea
      #single digit to double digit
      number="0#{@counter_ea}" if @counter_ea<10
      return "#{number} #{Templates::ITERATION_ENTWICKLUNG}"
    end
    #Softwareentwicklungs iteration name
    def current_se
      return "Iteration #{@counter_se}"
    end
  	#shapes the iterations for the current project tailoring
  	#and sets the current iteration to be used in the elements
  	#
  	#returns the appropriate iteration for the ep_ref given
  	def determine_iteration ep_ref
  	  @logger.debug("Determining iteration for #{ep_ref}. Current iteration #{@current_iteration}, previous ep_ref #{@previous_ep_ref}")
  	  @iterations[current_ea()]=Array.new unless @iterations[current_ea()] || @counter_ea==0
  	  if VMXT2TFS::Templates::EP_TO_ITERATION[ep_ref]
  	    #can we get the iteration simply?
	      @current_iteration=VMXT2TFS::Templates::EP_TO_ITERATION[ep_ref]
	      @wi_iteration=@current_iteration
	      @logger.debug("Iteration #{@current_iteration}")
	    else
  	    #is it an ep that starts an iteration?
    	  if VMXT2TFS::Templates::START_EA_ITERATION.include?(ep_ref)
    	    #did the previous one start an EA iteration?
    	    if !VMXT2TFS::Templates::START_EA_ITERATION.include?(@previous_ep_ref)
    	      @counter_ea+=1
      	    @counter_se=0
      	    @current_iteration=current_ea()
      	    @wi_iteration=current_ea()
      	    @logger.info("New EA iteration #{@current_iteration}")
    	    else
    	        @logger.info("EA iteration already started")
    	        @current_iteration=current_ea()
    	        @wi_iteration=current_ea()
  	      end
  	    end
    	  if VMXT2TFS::Templates::START_SE_ITERATION.include?(ep_ref)
    	    @counter_se+=1
    	    @current_iteration=current_se()
    	    @iterations[current_ea()]||=Array.new
    	    @iterations[current_ea()]<<@current_iteration
    	    @wi_iteration="#{current_ea()}\\#{current_se()}"
    	    @logger.info("New SE iteration #{@current_iteration}")
  	    end
      end
      @previous_ep_ref=ep_ref
	    return @wi_iteration 
  	end
  end

  #This class performs parsing of TFS ReportsTasks files to provide us data for the merging of the scaffolding with another TFS template.
  #
  class ReportParser
    include REXML
    #XPath to get a report definition
    XPATH_REPORT="/tasks/task/taskXml/ReportingServices/reports/report"
    #Returns an Array with Report instances
    def self.parse_reports report_xml,logger=nil
      logger||=VMXT2TFS.setup_logger
      logger.info("Loading report data")
      report_xml=Document.new(report_xml)
      reports=Array.new
      logger.debug("Parsing reports")
      t1=Time.now
      xml_reports=XPath.match(report_xml,XPATH_REPORT)
      xml_reports.each do |rep|
        logger.debug(rep)
        parameters=Array.new
        datasources=Array.new
        rep.get_elements("parameters/parameter").each do |el|
          parameters<<{:name=>el.attributes["name"].to_s,
            :value=>xml_escape(el.attributes["value"].to_s)}
        end
        logger.debug(parameters)
        datasources=Array.new
        rep.get_elements("datasources/reference").each do |el|
          datasources<<{:name=>el.attributes["name"].to_s,
            :dsname=>el.attributes["dsname"].to_s}
        end
        logger.debug(datasources)
        reports<<Report.new(:name=>rep.attributes["name"].to_s,
          :filename=>rep.attributes["filename"].to_s,
          :folder=>rep.attributes["folder"].to_s,
          :expiration=>rep.attributes["cacheExpiration"].to_s,
          :parameters=>parameters,
          :datasources=>datasources)
      end
      logger.info("Reports parsed in #{Time.now-t1} seconds")
      return reports
    end
    
    private 
    #Escapes the XML bad stuff
    def self.xml_escape txt
      txt.gsub!("&", "&amp;")
      txt.gsub!("<", "&lt;")
      txt.gsub!(">", "&gt;")
      txt.gsub!("'", "&apos;")
      txt.gsub!("\"", "&quot;")
      return txt
    end
  end  
end